package lv.solcraft.replication.client;

import java.util.List;

import lv.solcraft.replication.client.http.ServerPing;
import lv.solcraft.replication.client.logging.ClientLogger;
import lv.solcraft.replication.common.OperDTO;
import lv.solcraft.replication.common.OperStateDTO;
import lv.solcraft.replication.common.server.ExecutingFacade;
import lv.solcraft.replication.common.util.ShutdownEvent;

import org.apache.log4j.Logger;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;

public class OperReceiverImpl implements OperReceiver, ApplicationListener {

    private static Logger log = Logger.getLogger(OperReceiverImpl.class);

    private ClientConfig client;

    private ClientLogger clientLogger;

    private ExecutingFacade executingFacade;

    private OperExecutor operExecutor;

    private ExecutionStatus executionStatus;

    private ServerPing serverPing;

    private boolean stop;

    public ClientConfig getClient() {
        return client;
    }

    public void setClient(ClientConfig client) {
        this.client = client;
    }

    public ClientLogger getClientLogger() {
        return clientLogger;
    }

    public void setClientLogger(ClientLogger clientLogger) {
        this.clientLogger = clientLogger;
    }

    public ExecutingFacade getExecutingFacade() {
        return executingFacade;
    }

    public void setExecutingFacade(ExecutingFacade executingFacade) {
        this.executingFacade = executingFacade;
    }

    public void execute() {
        clientLogger.infoLocal("Oper sending, pinging server");
        if (!serverPing.ping()) {
            clientLogger.errorLocal("Skip oper execution, server ping failed");
            return;
        }
        try {
            executeInternal();
        } catch (RuntimeException e) {
            clientLogger.errorAll("Oper executing failed", e);
            throw e;
        }
    }

    private void executeInternal() {
        stop = false;

        clientLogger.infoAll("Starting to execute opers");

        client.reloadSettings();
        client.getSettings();

        executionStatus = new ExecutionStatus();
        int limit = client.getSettings().getMaxOpersPerSession();

        int count = 0;
        int oldCount = 0;

        while (count < limit) {
            List<OperDTO> opers = executingFacade.loadOpers(client.getServerName(), client.getSiteId());

            if (opers.isEmpty()) {
                clientLogger.infoAll("Finished to execute opers, total executed " + count + " opers; "
                        + executionStatus.commited + " commited, " + executionStatus.ignored + " ignored, "
                        + executionStatus.error + " errors");
                return;
            }

            if (stop) {
                throw new RuntimeException(
                        "Forcing transaction rollback in oper receiver (1), because shutdown event was received. Dont worry!");
            }

            // have to split execute and update states in two transactions
            List<OperStateDTO> states = executeOpers(opers);
            updateOperStates(states);

            // update current status
            for (OperStateDTO state : states) {
                if (state.isCommited()) {
                    executionStatus.commited++;
                } else if (state.isIgnored()) {
                    executionStatus.ignored++;
                } else if (state.isError()) {
                    executionStatus.error++;
                }
            }

            count += opers.size();
            if (oldCount + 100 <= count) {
                oldCount = count;
                clientLogger.infoAll("Executed total " + count + " opers; " + executionStatus.commited + " commited, "
                        + executionStatus.ignored + " ignored, " + executionStatus.error + " errors");
            }
        }
    }

    public OperExecutor getOperExecutor() {
        return operExecutor;
    }

    public void setOperExecutor(OperExecutor operExecutor) {
        this.operExecutor = operExecutor;
    }

    public List<OperStateDTO> executeOpers(List<OperDTO> opers) {
        List<OperStateDTO> states = operExecutor.execute(opers);
        if (stop) {
            throw new RuntimeException(
                    "Forcing transaction rollback in oper receiver (2), because shutdown event was received. Dont worry!");
        }
        return states;
    }

    public void updateOperStates(List<OperStateDTO> states) {
        executingFacade.updateOperStates(client.getServerName(), states, client.getSiteId());
    }

    private class ExecutionStatus {
        int commited;

        int ignored;

        int error;
    }

    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ShutdownEvent) {
            log.info("Received shutdown event");
            stop = true;
        }

    }

    public void setServerPing(ServerPing serverPing) {
        this.serverPing = serverPing;
    }

}
